#!/usr/bin/env python2
import os
import sys
import errno
import uuid
import getopt
import subprocess
import time
import threading
import random
import hashlib

from utils import Exp, _exec_system, _derror, _isip, _exec_remote,\
        mutil_exec, _random_str
from iscsi import Iscsi
from disk import Disk

THREAD_MAX = 100;
SNAP_MAX = 50;
BUFF_MAX = 4096;
VALID_OFFSET = 32;

class Test_snapclone():
    def __init__(self, test_type, thread, snap, ip, username, password, v):
        self.test_type = test_type
        self.thread = thread
        self.snap = snap
        self.snap_current = 0
        self.ip = ip
        self.username = username
        self.password = password
        self.verbose = v
        self.iscsi = Iscsi(v)
        self.srctgt = 'testsnapclone'
        self.destgt = 'testsnapvalidate'
        self.disk = Disk()
        self.srcdevs = None
        self.desdevs = None
        self.lich_tgt = "/opt/fusionstack/lich/libexec/lich.tgt"

    def prep(self):
        cmd = "%s -o new -m lun -t %s -l 0 --size 10G" % (self.lich_tgt, self.srctgt)
        if (self.verbose):
            print cmd
        (out, err) = _exec_remote(self.ip, cmd, self.username, self.password);
        if err.endswith('File exists\n'):
            raise Exp(errno.EEXIST, "/iscsi/default/%s/0 already exists" % self.srctgt)
        cmd = "%s -o new -m target -t %s" % (self.lich_tgt, self.destgt)
        if (self.verbose):
            print cmd
        (out, err) = _exec_remote(self.ip, cmd, self.username, self.password);

        self.iscsi.login_tgt(self.ip, self.srctgt)
        self.srcdevs = self.iscsi.getdev(self.srctgt)
        _exec_system("mkdir /mnt/%s" % self.srctgt)
        if self.test_type != 'd':
            print "srcdevs",self.srcdevs[0][1]
            self.disk.dev_format(self.srcdevs[0][1], self.verbose)
            try:
                self.disk.dev_mount(self.srcdevs[0][1], '/mnt/%s'%self.srctgt, self.verbose)
            except:
                pass

    def __execute_write_singlefile(self, uuid, snapthread):
        cnt = 0
        rand = 0
        fp = open("/mnt/%s/%s"%(self.srctgt, uuid), 'a');

        while True:
            rand = random.randint(1, 100)
            rang = [str(i) for i in range(cnt, cnt + rand)]
            cnt += rand
            content = '_'.join(rang) + '_'

            fp.write(content)
            fp.flush()
            _exec_system("sync", False)

            if not snapthread.is_alive():
                break;
            time.sleep(0.1)

        fp.close()

    def __execute_write_multifile(self, uuid, snapthread):
        dir_path = "/mnt/%s/%s"%(self.srctgt, uuid)
        os.mkdir(dir_path)

        file_index = 0
        while True:
            fp = open(os.path.join(dir_path, "%d.file"%file_index), 'w')

            randstr = _random_str(4096, 4096*4)
            md5str = hashlib.md5(randstr).hexdigest()

            fp.write(md5str + randstr)
            fp.close()
            _exec_system("sync", False)

            file_index += 1
            if not snapthread.is_alive():
                break;
            time.sleep(0.1)

    def __execute_write_device(self, uuid, snapthread):
        cnt = 0
        rand = 0
        offset = 0

        dev = os.path.join("/dev", self.srcdevs[0][1])
        fp = open(dev, 'w+')
        offstr = '0'*VALID_OFFSET
        fp.write(offstr)

        while True:
            rand = random.randint(1, 100)
            rang = [str(i) for i in range(cnt, cnt + rand)]
            cnt += rand
            content = '_'.join(rang) + '_'

            fp.seek(VALID_OFFSET + int(offstr))
            fp.write(content)

            offset += len(content)
            offstr = "%032d" % offset
            fp.seek(0)
            fp.write(offstr)

            fp.flush()
            _exec_system("sync", False)

            if not snapthread.is_alive():
                break;
            time.sleep(0.1)

        fp.close()

    def __execute_write(self, uuid, snapthread):
        if self.test_type == 's':
            self.__execute_write_singlefile(uuid, snapthread)
        elif self.test_type == 'm':
            self.__execute_write_multifile(uuid, snapthread)
        elif self.test_type == 'd':
            self.__execute_write_device(uuid, snapthread)

    def __execute_snapshot(self, snap):
        while True:
            if (self.snap_current == self.snap):
                break
            rand = random.randint(3, 10)
            time.sleep(rand)

            cmd = "/opt/fusionstack/lich/libexec/lich.snapshot --create /iscsi/default/%s/0@snap%s" %\
                    (self.srctgt, self.snap_current)
            if (self.verbose):
                print cmd
            (out, err) = _exec_remote(self.ip, cmd, self.username, self.password);
            if err.endswith('File exists\n'):
                raise Exp(errno.EEXIST, "/iscsi/default/%s/0@snap%s already exists" % (self.srctgt, self.snap_current))

            self.snap_current += 1

    def execute(self):
        snapthread = threading.Thread(target=self.__execute_snapshot, args=[self.snap])
        snapthread.start()

        if self.test_type == 'd':
            self.thread = 1

        args = []
        for i in range(self.thread):
            args.append([str(uuid.uuid1()), snapthread])
        mutil_exec(self.__execute_write, args)

        snapthread.join()

    def __validate_clone(self):
        for i in range(self.snap):
            cmd = "/opt/fusionstack/lich/libexec/lich.snapshot --clone /iscsi/default/%s/0@snap%s /iscsi/default/%s/%s" %\
                    (self.srctgt, i, self.destgt, i)
            if (self.verbose):
                print cmd
            (out, err) = _exec_remote(self.ip, cmd, self.username, self.password);
            if err.endswith('File exists\n'):
                raise Exp(errno.EEXIST, "/iscsi/default/%s/%s already exists" % (self.destgt, i))

    def __validate_mount(self):
        self.iscsi.login_tgt(self.ip, self.destgt)
        self.desdevs = self.iscsi.getdev(self.destgt)
        if self.test_type != 'd':
            for i in self.desdevs:
                _exec_system("mkdir /mnt/%s/%s -p" % (self.destgt, i[0]))
                try:
                    self.disk.dev_mount(i[1], '/mnt/%s/%s'%(self.destgt, i[0]), self.verbose)
                except:
                    pass

    def __validate_valid_scontent(self, file_path):
        fp = open(file_path, 'r')
        valid = 0
        last = ""

        while True:
            content = fp.read(BUFF_MAX)
            if not content:
                break

            try:
                content = last + content
                index = content.rindex('_')
                last = content[index + 1:].strip()
                content = content[:index]
            except:
                pass

            for i in content.split('_'):
                if i != str(valid):
                    fp.close()
                    raise Exp(errno.EPERM, "validate fail")
                valid += 1
        if last:
            if cmp(last, str(valid)[:len(last)]):
                fp.close()
                raise Exp(errno.EPERM, "validate fail")

        fp.close()

    def __validate_valid_singlefile(self):
        des_path = '/mnt/%s/'%self.destgt
        for i in os.listdir(des_path):
            dev_path = os.path.join(des_path, i)
            for f in os.listdir(dev_path):
                file_path = os.path.join(dev_path, f)
                if os.path.isfile(file_path):
                    print "validate:", file_path, " size:", os.path.getsize(file_path)
                    self.__validate_valid_scontent(file_path)

    def __validate_valid_mcontent(self, dir_path):
        file_index = 0
        while True:
            file_path = os.path.join(dir_path, "%d.file"%file_index)
            if not os.path.exists(file_path):
                break
            if os.path.getsize(file_path) == 0:
                print "validate:", file_path, " size:", os.path.getsize(file_path)
                file_index += 1
                continue

            fp = open(file_path, "r")
            refermd5 = fp.read(VALID_OFFSET)
            validstr = fp.read()
            cmpmd5 = hashlib.md5(validstr).hexdigest()
            fp.close()

            if refermd5 != cmpmd5:
                print "refermd5:%s, cmdmd5:%s" % (refermd5, cmpmd5)
                raise Exp(errno.EPERM, "validate fail")

            file_index += 1

    def __validate_valid_multifile(self):
        des_path = '/mnt/%s/'%self.destgt
        for i in os.listdir(des_path):
            dev_path = os.path.join(des_path, i)
            for d in os.listdir(dev_path):
                if len(d) != 36:
                    continue
                dir_path = os.path.join(dev_path, d)
                print "validate:", dir_path
                self.__validate_valid_mcontent(dir_path)

    def __validate_valid_dcontent(self, dev):
        fp = open(dev, 'r')
        offstr = fp.read(VALID_OFFSET)
        fp.seek(VALID_OFFSET)
        left = int(offstr)
        print "offset:", left
        valid = 0
        last = ""

        while left:
            size = BUFF_MAX if left > BUFF_MAX else left

            content = fp.read(size)
            if not content:
                fp.close()
                raise Exp(errno.EPERM, "validate fail")

            try:
                content = last + content
                index = content.rindex('_')
                last = content[index + 1:].strip()
                content = content[:index]
            except:
                pass

            for i in content.split('_'):
                if i != str(valid):
                    fp.close()
                    raise Exp(errno.EPERM, "validate fail")
                valid += 1

            left -= size

        if last:
            if cmp(last, str(valid)[:len(last)]):
                fp.close()
                raise Exp(errno.EPERM, "validate fail")

        fp.close()

    def __validate_valid_device(self):
        for i in self.desdevs:
            dev = os.path.join("/dev", i[1])
            print "validate:", dev,
            self.__validate_valid_dcontent(dev)

    def __validate_valid(self):
        if self.test_type == 's':
            self.__validate_valid_singlefile()
        elif self.test_type == 'm':
            self.__validate_valid_multifile()
        elif self.test_type == 'd':
            self.__validate_valid_device()

    def __clean_iscsi(self):
        if self.test_type != 'd':
            devs = self.iscsi.getdev(self.srctgt)
            try:
                self.disk.dev_umount(devs[0][1], self.verbose)
            except:
                pass
            devs = self.iscsi.getdev(self.destgt)
            for i in devs:
                try:
                    self.disk.dev_umount(i[1], self.verbose)
                except:
                    pass
        self.iscsi.logout("", "")

    def __clean_lich(self):
        for i in range(self.snap):
            cmd = "%s -o del -m lun -t %s -l %s" % (self.lich_tgt, self.destgt, i)
            if (self.verbose):
                print cmd
            (out, err) = _exec_remote(self.ip, cmd, self.username, self.password);

        cmd = "%s -o del -m target -t %s" % (self.lich_tgt, self.destgt)
        if (self.verbose):
            print cmd
        (out, err) = _exec_remote(self.ip, cmd, self.username, self.password);

        cmd = "%s -o del -m lun -t %s -l 0" % (self.lich_tgt, self.srctgt)
        if (self.verbose):
            print cmd
        (out, err) = _exec_remote(self.ip, cmd, self.username, self.password);

        cmd = "%s -o del -m target -t %s" % (self.lich_tgt, self.srctgt)
        if (self.verbose):
            print cmd
        (out, err) = _exec_remote(self.ip, cmd, self.username, self.password);

    def clean(self):
        self.__clean_iscsi()
        self.__clean_lich()

    def validate(self):
        self.__validate_clone()
        self.__validate_mount()
        self.__validate_valid()

    def start(self):
        self.prep()
        self.execute()
        self.validate()
        self.clean()

def usage():
    print ("usage:")
    print (sys.argv[0] + " --type <s(singlefile) | m(multifile) | d(device)>")
    print ("\t[--thread thread number] [--snap snapshot number] [--ip ip address]")
    print ("\t[--user username] [--pass password]")
    print ("\tdefault value: thread:10, snap:5, ip:127.0.0.1, user:root, pass:''")
    
def main():
    thread = 10
    snap = 5
    ip = "127.0.0.1"
    username = "root"
    password = ""
    verbose = True
    test_type = None
    
    try:
        opts, args = getopt.getopt(
            sys.argv[1:], 
            'hvt:', ['help', 'verbose', 'type=', 'thread=', 'snap=', 'ip=', 'user=', 'pass=']
            )
    except getopt.GetoptError, err:
        print str(err)
        usage()
        exit(errno.EINVAL)

    for o, a in opts:
        if o in ('-h', '--help'):
            usage()
            exit(0)
        elif o in ('-v', '--verbose'):
            verbose = True
        elif o in ('-t', '--type'):
            if a not in ('s', 'm', 'd', 'singlefile', 'multifile', 'device'):
                usage()
                exit(errno.EINVAL)
            test_type = a[:1]
        elif o in ('--thread'):
            try:
                thread = int(a)
                if (thread > THREAD_MAX):
                    _derror("THREAD_MAX:%s cannot set to %s" % (THREAD_MAX, a))
                    exit(errno.EINVAL)
            except:
                pass
        elif o in ('--snap'):
            try:
                snap = int(a)
                if (snap > SNAP_MAX):
                    _derror("SNAP_MAX:%s cannot set to %s" % (SNAP_MAX, a))
                    exit(errno.EINVAL)
            except:
                pass
        elif o in ('--ip'):
            if _isip(a):
                ip = a
            else:
                _derror("%s invalid" % a)
                exit(errno.EINVAL)
        elif o in ('--user'):
            username = a
        elif o in ('--pass'):
            password = a
        else:
            usage()
            exit(errno.EINVAL)
            
    test_snapclone = Test_snapclone(test_type, thread, snap, ip, username, password, verbose)
    try:
        test_snapclone.start()
    except Exp, e:
        raise
        _derror(e.err)
        exit(e.errno)

if __name__ == '__main__':
    if (len(sys.argv) == 1):
        usage()
    else:
        main()
